package cn.gzten.annotation.processor;

import cn.gzten.annotation.RegisterReflection;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.auto.service.AutoService;
import org.xml.sax.SAXException;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.StandardLocation;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

@SupportedSourceVersion(SourceVersion.RELEASE_11)
@AutoService(Processor.class)
public class RegisterReflectionProcessor extends AbstractProcessor {
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    private Filer filer;
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.filer = processingEnv.getFiler();
        System.out.println("RegisterReflectionProcessor done initialization!");
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Set.of(RegisterReflection.class.getCanonicalName());
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        processListOfClasses(roundEnv.getElementsAnnotatedWith(RegisterReflection.class), filer);

        return true;
    }

    public static void processListOfClasses(Set<? extends Element> elements, Filer filer) {
        AtomicBoolean runOnce = new AtomicBoolean(true);
        List<Map<String, Object>> reflectList = new LinkedList<>();
        List<Path> reflectPaths = new LinkedList<>();
        AtomicBoolean reflectChanged = new AtomicBoolean(false);

        elements.forEach(e -> {
            String classFullName = e.toString();

            if (runOnce.get()) {
                runOnce.set(false);
                try {
                    var classFullPath = classFullName.replace('.', '/') + ".java";
                    var source = filer.getResource(StandardLocation.SOURCE_PATH,
                            "",
                            classFullPath);

                    URI pomUri = URI.create(source.toUri().toString().replace("src/main/java/" + classFullPath, "pom.xml"));
                    if (Files.exists(Paths.get(pomUri))) {
                        String artifactId = null;
                        String groupId = null;

                        String xml = Files.readString(Paths.get(pomUri));
                        var document = DocumentBuilderFactory.newDefaultInstance().newDocumentBuilder().parse(pomUri.toString());
                        var nodes = document.getChildNodes();

                        for (int i=0; i < nodes.getLength(); i++) {
                            var node = nodes.item(i);
                            if (node.getNodeName().equals("project")) {
                                nodes = node.getChildNodes();
                                boolean foundArtifact = false;
                                boolean foundGroup = false;
                                boolean foundParentGroup = false;
                                String parentGroup = null;

                                for (i=0; i < nodes.getLength(); i++) {
                                    node = nodes.item(i);
                                    if (node.getNodeName().equals("artifactId")) {
                                        artifactId = node.getTextContent();
                                        System.out.println("Artifact id is: " + artifactId);
                                        foundArtifact = true;
                                    }
                                    if (node.getNodeName().equals("groupId")) {
                                        groupId = node.getTextContent();
                                        System.out.println("Group id is: " + groupId);
                                        foundGroup = true;
                                    }
                                    if (node.getNodeName().equals("parent")) {
                                        var parentNodes = node.getChildNodes();
                                        for (int j=0; j < parentNodes.getLength(); j++) {
                                            var parentNode = parentNodes.item(j);
                                            if (parentNode.getNodeName().equals("groupId")) {
                                                parentGroup = parentNode.getTextContent();
                                                System.out.println("Parent group id is: " + parentGroup);
                                                foundParentGroup = true;
                                                break;
                                            }
                                        }
                                    }

                                    if (foundArtifact && (foundGroup || foundParentGroup)) {
                                        if (!foundGroup && foundParentGroup) {
                                            groupId = parentGroup;
                                        }
                                        break;
                                    }
                                }
                                break;
                            }
                        }

                        String myPath = source.toUri().toString().replace("java/" + classFullPath,
                                String.format("resources/META-INF/native-image/%s/%s/reflect-config.json", groupId, artifactId));
                        var uri = URI.create(myPath);

                        var reflectPath = Paths.get(uri);
                        reflectPaths.add(reflectPath);
                        if (Files.exists(reflectPath)) {
                            reflectList.addAll(OBJECT_MAPPER.readValue(uri.toURL(), List.class));
                        } else {
                            Files.createDirectories(reflectPath.getParent());
                        }
                    }
                } catch (IOException ex) {
                    ex.printStackTrace();
                } catch (ParserConfigurationException ex) {
                    ex.printStackTrace();
                } catch (SAXException ex) {
                    ex.printStackTrace();
                }
            }

            AtomicBoolean foundReflect = new AtomicBoolean(false);
            reflectList.forEach(m -> {
                if (m.get("name").equals(classFullName)) {
                    foundReflect.set(true);
                    return;
                }
            });

            if (!foundReflect.get()) {
                Map<String, Object> m = new HashMap<>();
                m.put("name", classFullName);
                m.put("allDeclaredConstructors", true);
                m.put("allPublicConstructors", true);
                m.put("allDeclaredMethods", true);
                m.put("allPublicMethods", true);
                m.put("allDeclaredFields", true);
                m.put("allPublicFields", true);
                reflectList.add(m);
                reflectChanged.set(true);
            }
        });

        if(!reflectPaths.isEmpty() && reflectChanged.get()) {
            var path = reflectPaths.get(0);
            try {
                OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValue(path.toFile(), reflectList);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}
